﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceProcess;


using NLog;

namespace ServiceHosting
{
    public class FlexibleServiceHost : ServiceBase
    {
        public enum ServiceHostType
        {
            Console = 0,
            WindowsService = 1
        };

        // Logger
        private static Logger log = LogManager.GetCurrentClassLogger();
        
        // Unhandled exception handler
        private static UnhandledExceptionEventHandler threadExceptionHandler = 
            new UnhandledExceptionEventHandler(ThreadExceptionHandler);


        private static FlexibleServiceHost mServiceHost = null;
        private static object mSyncRoot = new object();
        private static volatile bool mHandlerAssigned = false;
        private static volatile bool mHostedServiceRunning = false;

        static FlexibleServiceHost()
        {

        }

        public static Logger Log
        {
            get { return log; }
        }

        public static bool HostInitialized
        {
            get
            {
                lock (mSyncRoot)
                {
                    return (mServiceHost != null);
                }
            }
        }

        public static bool HostRunning
        {
            get { return mHostedServiceRunning; }
        }

        public static FlexibleServiceHost ServiceHost
        {
            get
            {
                lock (mSyncRoot)
                {
                    return (mServiceHost);
                }
            }
        }

        protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e)
        {
            log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject);
            TerminateHosting((Exception)e.ExceptionObject);
        }

        protected static void StartServiceHost(FlexibleServiceHost host, string[] args)
        {
            // Check for no hosted service
            if (!HostInitialized)
                return;

            // Try to start the service
            try
            {
                host.HostedService.Start(args);
                mHostedServiceRunning = true;

                log.Info("Now hosting: {0}.", (host.HostedService).GetType().Name);
            }
            catch (Exception ex)
            {
                log.FatalException("Could not start hosted service '" + host.HostedService.GetType().Name + "'.", ex);

                // Check if we need to dispose of the hosted service
                if (host.mIsHostedServiceDisposable)
                {
                    try
                    {
                        IDisposable disposer = (IDisposable)host.HostedService;
                        disposer.Dispose();
                    }
                    catch (Exception disEx)
                    {
                        log.ErrorException("Could not dispose of the hosted service, even though it implements IDisposable.", disEx);
                    }
                }

                // If we are hosting from within a windows service, exit the environment. If it is a console host,
                // rethrow the exception.
                if (host.HostingType == ServiceHostType.WindowsService)
                    Environment.Exit(1);
                else
                    throw;
            }
        }

        protected static void StopServiceHost(FlexibleServiceHost host)
        {
            // Check for no host
            if (!HostInitialized)
                return;

            // Use this to track what to rethrow later
            Exception reThrow = null;

            // Log that we are stopping the hosted service
            log.Info("Stop requested for {0}...", mServiceHost.ServiceName);

            // Check to see if we need to shutdown and stop the service
            if (mHostedServiceRunning)
            {
                // Make sure we handle 
                try
                {
                    host.HostedService.BeginShutdown();
                    host.HostedService.Stop();
                    log.Info("Gracefully stopped hosted service: {0}.", host.HostedService.GetType().Name);
                }
                catch (Exception ex)
                {
                    reThrow = ex;
                    log.FatalException("Could not properly stop and shutdown hosted service.", ex);
                }
                finally
                {
                    mHostedServiceRunning = false;
                }
            }

            // Next, attempt to dispose of the hosted service if necessary
            if (host.mIsHostedServiceDisposable)
            {
                // Make sure we handle
                try
                {
                    IDisposable disposable = (IDisposable)host.HostedService;
                    disposable.Dispose();
                    log.Info("Properly disposed of hosted service: {0}.", host.HostedService.GetType().Name);
                }
                catch (Exception ex)
                {
                    if (reThrow == null) { reThrow = ex; }
                    log.ErrorException("Could not properly dispose of hosted service's resources.", ex);
                }
            }

            // Finally, mark all that junk as "null"
            lock (mSyncRoot)
            {
                mServiceHost = null;
            }

            // Finally, figure out what to do
            if (host.HostingType == ServiceHostType.WindowsService)
            {
                if (reThrow != null)
                    Environment.Exit(1);
                else
                    return;
            }
            else
            {
                if (reThrow != null)
                    throw reThrow;
                else
                    return;
            }
        }

        public static void BeginHosting()
        {
            if (!HostInitialized)
            {
                throw new InvalidOperationException("Hosted service must be initialized first.");
            }
            
            // Log about the hosting type
            log.Info("Starting {0} {1}...", mServiceHost.ServiceName, (mServiceHost.HostingType == ServiceHostType.Console) ?
                "in console mode." : "as a service.");

            if (ServiceHost.HostingType == ServiceHostType.WindowsService)
            {                
                ServiceBase.Run(FlexibleServiceHost.ServiceHost);
            }
            else
            {
                ServiceHost.OnStart(null);
            }
        }

        public static void EndHosting()
        {
            if (HostInitialized)
            {
                ServiceHost.OnStop();
            }
        }

        public static void TerminateHosting(Exception faultEx)
        {
            if (HostInitialized)
            {
                ServiceHost.OnStop();
            }

            // Finally, figure out how to exit
            if (ServiceHost.HostingType == ServiceHostType.Console)
            {
                throw faultEx;
            }
            else
            {
                Environment.Exit(1);
            }

        }

        public static void InitializeHostedService<T>(string serviceName, ServiceHostType hostType)
            where T : IHostableService
        {
            InitializeHostedService<T>(serviceName, hostType, null);
        }

        public static void InitializeHostedService<T>(string serviceName, ServiceHostType hostType, 
            params object[] initializerArguments) where T : IHostableService
        {
            // Instance of the hosted type
            IHostableService hostedService = null;

            // Try to instantiate a new instance of the provided type
            try
            {
                hostedService = (IHostableService)Activator.CreateInstance(typeof(T), initializerArguments);
            }
            catch (Exception ex)
            {
                log.FatalException("Could not create instance of " + typeof(T).Name + "!", ex);

                if (hostType == ServiceHostType.Console)
                    throw;
                else
                    Environment.Exit(1);
            }

            // Now setup the hosted instance
            lock (mSyncRoot)
            {
                mServiceHost = new FlexibleServiceHost(serviceName, hostType, hostedService);
            }
        }

        public static bool InstallWindowsServiceHost(string serviceName, string displayName, string serviceDescription)
        {
            ServiceInstaller svcInstaller = new ServiceInstaller(serviceName, displayName, serviceDescription);

            return svcInstaller.InstallService();
        }

        public static bool InstallWindowsServiceHost(string serviceName, string displayName, string serviceDescription,
            string username, string password)
        {
            ServiceInstaller svcInstaller = new ServiceInstaller(serviceName, displayName, serviceDescription);

            return svcInstaller.InstallService(username, password);

        }

        public static void UninstallWindowsServiceHost(string serviceName)
        {
            ServiceInstaller svcInstaller = new ServiceInstaller(serviceName, null, null);

            svcInstaller.UninstallService();
        }


        #region Instance Specific functionality

        private ServiceHostType mHostType;
        private IHostableService mHostedService;
        protected bool mIsHostedServiceDisposable;

        /// <summary>
        /// Used only by the installer.
        /// </summary>
        /// <param name="serviceName">The name of the service to install.</param>
        private FlexibleServiceHost(string serviceName)
        {
            base.ServiceName = serviceName;
        }

        private FlexibleServiceHost(string serviceName, ServiceHostType hostType, IHostableService hostedService)
        {
            base.ServiceName = serviceName;
            mHostedService = hostedService;
            mHostType = hostType;

            // Check for disposability
            IDisposable disposable = hostedService as IDisposable;
            mIsHostedServiceDisposable = (disposable != null);
        }

        public ServiceHostType HostingType
        {
            get { return mHostType; }
        }

        public IHostableService HostedService
        {
            get { return mHostedService; }
        }

        protected override void OnStart(string[] args)
        {
            // Hook up the global exception handler
            AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler;
            mHandlerAssigned = true;
 
            // Start the service host
            FlexibleServiceHost.StartServiceHost(this, args);
        }

        protected override void OnStop()
        {
            // Stop the service host
            FlexibleServiceHost.StopServiceHost(this);

            // Check to see if the global error handler needs to be removed.
            if (mHandlerAssigned)
            {
                AppDomain.CurrentDomain.UnhandledException -= threadExceptionHandler;
                mHandlerAssigned = false;
            }
        }

        #endregion

    }
}
